{ *********************************************************************** }
{                                                                         }
{ Delphi Runtime Library                                                  }
{                                                                         }
{ Copyright (c) 1996,2001 Borland Software Corporation                    }
{                                                                         }
{ *********************************************************************** }

// Three layers:
// - Address space administration
// - Committed space administration
// - Suballocator
//
// Helper module: administrating block descriptors
//


//
// Operating system interface
//
const
  LMEM_FIXED = 0;
  LMEM_ZEROINIT = $40;

  MEM_COMMIT   = $1000;
  MEM_RESERVE  = $2000;
  MEM_DECOMMIT = $4000;
  MEM_RELEASE  = $8000;

  PAGE_NOACCESS  = 1;
  PAGE_READWRITE = 4;

type
  DWORD = Integer;
  BOOL  = LongBool;

  TRTLCriticalSection = packed record
    DebugInfo: Pointer;
    LockCount: Longint;
    RecursionCount: Longint;
    OwningThread: Integer;
    LockSemaphore: Integer;
    Reserved: DWORD;
  end;

{function LocalAlloc(flags, size: Integer): Pointer; stdcall;
  external kernel name 'LocalAlloc';
function LocalFree(addr: Pointer): Pointer; stdcall;
  external kernel name 'LocalFree';}

function VirtualAlloc(lpAddress: Pointer;
  dwSize, flAllocationType, flProtect: DWORD): Pointer; stdcall;
  external kernel name 'VirtualAlloc';
function VirtualFree(lpAddress: Pointer; dwSize, dwFreeType: DWORD): BOOL; stdcall;
  external kernel name 'VirtualFree';

procedure InitializeCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;
  external kernel name 'InitializeCriticalSection';
procedure EnterCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;
  external kernel name 'EnterCriticalSection';
procedure LeaveCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;
  external kernel name 'LeaveCriticalSection';
procedure DeleteCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;
  external kernel name 'DeleteCriticalSection';

// Common Data structure:

type
  TBlock = packed record
    addr: PChar;
    size: Integer;
  end;

// Heap error codes

const
  cHeapOk           = 0;          // everything's fine
  cReleaseErr       = 1;          // operating system returned an error when we released
  cDecommitErr      = 2;          // operating system returned an error when we decommited
  cBadCommittedList = 3;          // list of committed blocks looks bad
  cBadFiller1       = 4;          // filler block is bad
  cBadFiller2       = 5;          // filler block is bad
  cBadFiller3       = 6;          // filler block is bad
  cBadCurAlloc      = 7;          // current allocation zone is bad
  cCantInit         = 8;          // couldn't initialize
  cBadUsedBlock     = 9;          // used block looks bad
  cBadPrevBlock     = 10;         // prev block before a used block is bad
  cBadNextBlock     = 11;         // next block after a used block is bad
  cBadFreeList      = 12;         // free list is bad
  cBadFreeBlock     = 13;         // free block is bad
  cBadBalance       = 14;         // free list doesn't correspond to blocks marked free

var
  initialized   : Boolean;
  heapErrorCode : Integer;
  heapLock      : TRTLCriticalSection;
  {X} // Handler to set it to UninitAllocator, if Delphi memory manager used:
  {X} UninitMemoryManager : procedure = DummyProc;

//
// Helper module: administrating block descriptors.
//
type
  PBlockDesc = ^TBlockDesc;
  TBlockDesc = packed record
    next: PBlockDesc;
    prev: PBlockDesc;
    addr: PChar;
    size: Integer;
  end;

type
  PBlockDescBlock = ^TBlockDescBlock;
  TBlockDescBlock = packed record
    next: PBlockDescBlock;
    data: array [0..99] of TBlockDesc;
  end;

var
  blockDescBlockList: PBlockDescBlock;
  blockDescFreeList : PBlockDesc;


function GetBlockDesc: PBlockDesc;
// Get a block descriptor.
// Will return nil for failure.
var
  bd:  PBlockDesc;
  bdb: PBlockDescBlock;
  i:   Integer;
begin
  if blockDescFreeList = nil then begin
    bdb := LocalAlloc(LMEM_FIXED, sizeof(bdb^));
    if bdb = nil then begin
      result := nil;
      exit;
    end;
    bdb.next := blockDescBlockList;
    blockDescBlockList := bdb;
    for i := low(bdb.data) to high(bdb.data) do begin
      bd := @bdb.data[i];
      bd.next := blockDescFreeList;
      blockDescFreeList := bd;
    end;
  end;
  bd := blockDescFreeList;
  blockDescFreeList := bd.next;
  result := bd;
end;


procedure MakeEmpty(bd: PBlockDesc);
begin
  bd.next := bd;
  bd.prev := bd;
end;


function AddBlockAfter(prev: PBlockDesc; const b: TBlock): Boolean;
var
  next, bd: PBlockDesc;
begin
  bd := GetBlockDesc;
  if bd = nil then
    result := False
  else begin
    bd.addr := b.addr;
    bd.size := b.size;

    next := prev.next;
    bd.next := next;
    bd.prev := prev;
    next.prev := bd;
    prev.next := bd;

    result := True;
  end;
end;


procedure DeleteBlock(bd: PBlockDesc);
var
  prev, next: PBlockDesc;
begin
  prev := bd.prev;
  next := bd.next;
  prev.next := next;
  next.prev := prev;
  bd.next := blockDescFreeList;
  blockDescFreeList := bd;
end;


function MergeBlockAfter(prev: PBlockDesc; const b: TBlock) : TBlock;
var
  bd, bdNext: PBlockDesc;
begin
  bd := prev.next;
  result := b;
  repeat
    bdNext := bd.next;
    if bd.addr + bd.size = result.addr then begin
      DeleteBlock(bd);
      result.addr := bd.addr;
      inc(result.size, bd.size);
    end else if result.addr + result.size = bd.addr then begin
      DeleteBlock(bd);
      inc(result.size, bd.size);
    end;
    bd := bdNext;
  until bd = prev;
  if not AddBlockAfter(prev, result) then
    result.addr := nil;
end;


function RemoveBlock(bd: PBlockDesc; const b: TBlock): Boolean;
var
  n: TBlock;
  start: PBlockDesc;
begin
  start := bd;
  repeat
    if (bd.addr <= b.addr) and (bd.addr + bd.size >= b.addr + b.size) then begin
      if bd.addr = b.addr then begin
        Inc(bd.addr, b.size);
        Dec(bd.size, b.size);
        if bd.size = 0 then
          DeleteBlock(bd);
      end else if bd.addr + bd.size = b.addr + b.size then
        Dec(bd.size, b.size)
      else begin
        n.addr := b.addr + b.size;
        n.size := bd.addr + bd.size - n.addr;
        bd.size := b.addr - bd.addr;
        if not AddBlockAfter(bd, n) then begin
          result := False;
          exit;
        end;
      end;
      result := True;
      exit;
    end;
    bd := bd.next;
  until bd = start;
  result := False;
end;



//
// Address space administration:
//

const
  cSpaceAlign = 64*1024;
  cSpaceMin   = 1024*1024;
  cPageAlign  = 4*1024;

var
  spaceRoot: TBlockDesc;


function GetSpace(minSize: Integer): TBlock;
// Get at least minSize bytes address space.
// Success: returns a block, possibly much bigger than requested.
// Will not fail - will raise an exception or terminate program.
begin
  if minSize < cSpaceMin then
    minSize := cSpaceMin
  else
    minSize := (minSize + (cSpaceAlign-1)) and not (cSpaceAlign-1);

  result.size := minSize;
  result.addr := VirtualAlloc(nil, minSize, MEM_RESERVE, PAGE_NOACCESS);
  if result.addr = nil then
    exit;

  if not AddBlockAfter(@spaceRoot, result) then begin
    VirtualFree(result.addr, 0, MEM_RELEASE);
    result.addr := nil;
    exit;
  end;
end;


function GetSpaceAt(addr: PChar; minSize: Integer): TBlock;
// Get at least minSize bytes address space at addr.
// Return values as above.
// Failure: returns block with addr = nil.
begin
  result.size := cSpaceMin;
  result.addr := VirtualAlloc(addr, cSpaceMin, MEM_RESERVE, PAGE_READWRITE);
  if result.addr = nil then begin
    minSize := (minSize + (cSpaceAlign-1)) and not (cSpaceAlign-1);
    result.size := minSize;
    result.addr := VirtualAlloc(addr, minSize, MEM_RESERVE, PAGE_READWRITE);
  end;
  if result.addr <> nil then begin
    if not AddBlockAfter(@spaceRoot, result) then begin
      VirtualFree(result.addr, 0, MEM_RELEASE);
      result.addr := nil;
    end;
  end;
end;


function FreeSpace(addr: Pointer; maxSize: Integer): TBlock;
// Free at most maxSize bytes of address space at addr.
// Returns the block that was actually freed.
var
  bd, bdNext: PBlockDesc;
  minAddr, maxAddr, startAddr, endAddr: PChar;
begin
  minAddr := PChar($FFFFFFFF);
  maxAddr := nil;
  startAddr := addr;
  endAddr   := startAddr + maxSize;
  bd := spaceRoot.next;
  while bd <> @spaceRoot do begin
    bdNext := bd.next;
    if (bd.addr >= startAddr) and (bd.addr + bd.size <= endAddr) then begin
      if minAddr > bd.addr then
        minAddr := bd.addr;
      if maxAddr < bd.addr + bd.size then
        maxAddr := bd.addr + bd.size;
      if not VirtualFree(bd.addr, 0, MEM_RELEASE) then
        heapErrorCode := cReleaseErr;
      DeleteBlock(bd);
    end;
    bd := bdNext;
  end;
  result.addr := nil;
  if maxAddr <> nil then begin
    result.addr := minAddr;
    result.size := maxAddr - minAddr;
  end;
end;


function Commit(addr: Pointer; minSize: Integer): TBlock;
// Commits memory.
// Returns the block that was actually committed.
// Will return a block with addr = nil on failure.
var
  bd: PBlockDesc;
  loAddr, hiAddr, startAddr, endAddr: PChar;
begin
  startAddr := PChar(Integer(addr) and not (cPageAlign-1));
  endAddr := PChar(((Integer(addr) + minSize) + (cPageAlign-1)) and not (cPageAlign-1));
  result.addr := startAddr;
  result.size := endAddr - startAddr;
  bd := spaceRoot.next;
  while bd <> @spaceRoot do begin
    // Commit the intersection of the block described by bd and [startAddr..endAddr)
    loAddr := bd.addr;
    hiAddr := loAddr + bd.size;
    if loAddr < startAddr then
      loAddr := startAddr;
    if hiAddr > endAddr then
      hiAddr := endAddr;
    if loAddr < hiAddr then begin
      if VirtualAlloc(loAddr, hiAddr - loAddr, MEM_COMMIT, PAGE_READWRITE) = nil then begin
        result.addr := nil;
        exit;
      end;
    end;
    bd := bd.next;
  end;
end;


function Decommit(addr: Pointer; maxSize: Integer): TBlock;
// Decommits address space.
// Returns the block that was actually decommitted.
var
  bd: PBlockDesc;
  loAddr, hiAddr, startAddr, endAddr: PChar;
begin
  startAddr := PChar((Integer(addr) + + (cPageAlign-1)) and not (cPageAlign-1));
  endAddr := PChar((Integer(addr) + maxSize) and not (cPageAlign-1));
  result.addr := startAddr;
  result.size := endAddr - startAddr;
  bd := spaceRoot.next;
  while bd <> @spaceRoot do begin
    // Decommit the intersection of the block described by bd and [startAddr..endAddr)
    loAddr := bd.addr;
    hiAddr := loAddr + bd.size;
    if loAddr < startAddr then
      loAddr := startAddr;
    if hiAddr > endAddr then
      hiAddr := endAddr;
    if loAddr < hiAddr then begin
      if not VirtualFree(loAddr, hiAddr - loAddr, MEM_DECOMMIT) then
        heapErrorCode := cDecommitErr;
    end;
    bd := bd.next;
  end;
end;


//
// Committed space administration
//
const
  cCommitAlign = 16*1024;

var
  decommittedRoot: TBlockDesc;


function GetCommitted(minSize: Integer): TBlock;
// Get a block of committed memory.
// Returns a committed memory block, possibly much bigger than requested.
// Will return a block with a nil addr on failure.
var
  bd: PBlockDesc;
begin
  minSize := (minSize + (cCommitAlign-1)) and not (cCommitAlign-1);
  repeat
    bd := decommittedRoot.next;
    while bd <> @decommittedRoot do begin
      if bd.size >= minSize then begin
        result := Commit(bd.addr, minSize);
        if result.addr = nil then
          exit;
        Inc(bd.addr, result.size);
        Dec(bd.size, result.size);
        if bd.size = 0 then
          DeleteBlock(bd);
        exit;
      end;
      bd := bd.next;
    end;
    result := GetSpace(minSize);
    if result.addr = nil then
      exit;
    if MergeBlockAfter(@decommittedRoot, result).addr = nil then begin
      FreeSpace(result.addr, result.size);
      result.addr := nil;
      exit;
    end;
  until False;
end;


function GetCommittedAt(addr: PChar; minSize: Integer): TBlock;
// Get at least minSize bytes committed space at addr.
// Success: returns a block, possibly much bigger than requested.
// Failure: returns a block with addr = nil.
var
  bd: PBlockDesc;
  b: TBlock;
begin
  minSize := (minSize + (cCommitAlign-1)) and not (cCommitAlign-1);
  repeat

    bd := decommittedRoot.next;
    while (bd <> @decommittedRoot) and (bd.addr <> addr) do
      bd := bd.next;

    if bd.addr = addr then begin
      if bd.size >= minSize then
        break;
      b := GetSpaceAt(bd.addr + bd.size, minSize - bd.size);
      if b.addr <> nil then begin
        if MergeBlockAfter(@decommittedRoot, b).addr <> nil then
          continue
        else begin
          FreeSpace(b.addr, b.size);
          result.addr := nil;
          exit;
        end;
      end;
    end;

    b := GetSpaceAt(addr, minSize);
    if b.addr = nil then
      break;

    if MergeBlockAfter(@decommittedRoot, b).addr = nil then begin
      FreeSpace(b.addr, b.size);
      result.addr := nil;
      exit;
    end;
  until false;

  if (bd.addr = addr) and (bd.size >= minSize) then begin
    result := Commit(bd.addr, minSize);
    if result.addr = nil then
      exit;
    Inc(bd.addr, result.size);
    Dec(bd.size, result.size);
    if bd.size = 0 then
      DeleteBlock(bd);
  end else
    result.addr := nil;
end;


function FreeCommitted(addr: PChar; maxSize: Integer): TBlock;
// Free at most maxSize bytes of address space at addr.
// Returns the block that was actually freed.
var
  startAddr, endAddr: PChar;
  b: TBlock;
begin
  startAddr := PChar(Integer(addr + (cCommitAlign-1)) and not (cCommitAlign-1));
  endAddr := PChar(Integer(addr + maxSize) and not (cCommitAlign-1));
  if endAddr > startAddr then begin
    result := Decommit(startAddr, endAddr - startAddr);
    b := MergeBlockAfter(@decommittedRoot, result);
    if b.addr <> nil then
      b := FreeSpace(b.addr, b.size);
    if b.addr <> nil then
      RemoveBlock(@decommittedRoot, b);
  end else
    result.addr := nil;
end;


//
// Suballocator (what the user program actually calls)
//

type
  PFree = ^TFree;
  TFree = packed record
    prev: PFree;
    next: PFree;
    size: Integer;
  end;
  PUsed = ^TUsed;
  TUsed = packed record
    sizeFlags: Integer;
  end;

const
  cAlign        = 4;
  cThisUsedFlag = 2;
  cPrevFreeFlag = 1;
  cFillerFlag   = Integer($80000000);
  cFlags        = cThisUsedFlag or cPrevFreeFlag or cFillerFlag;
  cSmallSize    = 4*1024;
  cDecommitMin  = 15*1024;

type
  TSmallTab    = array [sizeof(TFree) div cAlign .. cSmallSize div cAlign] of PFree;

VAR
  avail        : TFree;
  rover        : PFree;
  remBytes     : Integer;
  curAlloc     : PChar;
  smallTab     : ^TSmallTab;
  committedRoot: TBlockDesc;


{X} // UninitAllocator - placed before InitAllocator to refer to.
procedure UninitAllocator;
// Shutdown.
var
  bdb: PBlockDescBlock;
  bd : PBlockDesc;
begin
  if initialized then begin
    try
      if IsMultiThread then EnterCriticalSection(heapLock);

      initialized := False;

      LocalFree(smallTab);
      smallTab := nil;

      bd := spaceRoot.next;
      while bd <> @spaceRoot do begin
        VirtualFree(bd.addr, 0, MEM_RELEASE);
        bd := bd.next;
      end;

      MakeEmpty(@spaceRoot);
      MakeEmpty(@decommittedRoot);
      MakeEmpty(@committedRoot);

      bdb := blockDescBlockList;
      while bdb <> nil do begin
        blockDescBlockList := bdb^.next;
        LocalFree(bdb);
        bdb := blockDescBlockList;
      end;
    finally
      if IsMultiThread then LeaveCriticalSection(heapLock);
      DeleteCriticalSection(heapLock);
    end;
  end;
end;

function InitAllocator: Boolean;
// Initialize. No other calls legal before that.
var
  i: Integer;
  a: PFree;
begin
  try
    InitializeCriticalSection(heapLock);
    if IsMultiThread then EnterCriticalSection(heapLock);

    MakeEmpty(@spaceRoot);
    MakeEmpty(@decommittedRoot);
    MakeEmpty(@committedRoot);

    smallTab := LocalAlloc(LMEM_FIXED, sizeof(smallTab^));
    if smallTab <> nil then begin
      for i:= low(smallTab^) to high(smallTab^) do
        smallTab[i] := nil;

      a := @avail;
      a.next := a;
      a.prev := a;
      rover := a;

      initialized := True;
      {X} // set here handler UninitMemoryManager to UninitAllocator }
      {X} UninitMemoryManager := UninitAllocator;
    end;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
  result := initialized;
end;



procedure DeleteFree(f: PFree);
var
  n, p: PFree;
  size: Integer;
begin
  if rover = f then
    rover := f.next;
  n := f.next;
  size := f.size;
  if size <= cSmallSize then begin
    if n = f then
      smallTab[size div cAlign] := nil
    else begin
      smallTab[size div cAlign] := n;
      p := f.prev;
      n.prev := p;
      p.next := n;
    end;
  end else begin
    p := f.prev;
    n.prev := p;
    p.next := n;
  end;
end;


procedure InsertFree(a: Pointer; size: Integer); forward;


function FindCommitted(addr: PChar): PBlockDesc;
begin
  result := committedRoot.next;
  while result <> @committedRoot do begin
    if (addr >= result.addr) and (addr < result.addr + result.size) then
      exit;
    result := result.next;
  end;
  heapErrorCode := cBadCommittedList;
  result := nil;
end;


procedure FillBeforeGap(a: PChar; size: Integer);
var
  rest: Integer;
  e: PUsed;
begin
  rest := size - sizeof(TUsed);
  e := PUsed(a + rest);
  if size >= sizeof(TFree) + sizeof(TUsed) then begin
    e.sizeFlags := sizeof(TUsed) or cThisUsedFlag or cPrevFreeFlag or cFillerFlag;
    InsertFree(a, rest);
  end else if size >= sizeof(TUsed) then begin
    PUsed(a).sizeFlags := size or (cThisUsedFlag or cFillerFlag);
    e.sizeFlags := size or (cThisUsedFlag or cFillerFlag);
  end;
end;


procedure InternalFreeMem(a: PChar);
begin
  Inc(AllocMemCount);
  Inc(AllocMemSize,PUsed(a-sizeof(TUsed)).sizeFlags and not cFlags - sizeof(TUsed));
  SysFreeMem(a);
end;


procedure FillAfterGap(a: PChar; size: Integer);
begin
  if size >= sizeof(TFree) then begin
    PUsed(a).sizeFlags := size or cThisUsedFlag;
    InternalFreeMem(a + sizeof(TUsed));
  end else begin
    if size >= sizeof(TUsed) then
      PUsed(a).sizeFlags := size or (cThisUsedFlag or cFillerFlag);
    Inc(a,size);
    PUsed(a).sizeFlags := PUsed(a).sizeFlags and not cPrevFreeFlag;
  end;
end;


function FillerSizeBeforeGap(a: PChar): Integer;
var
  sizeFlags : Integer;
  freeSize  : Integer;
  f : PFree;
begin
  sizeFlags := PUsed(a - sizeof(TUsed)).sizeFlags;
  if (sizeFlags and (cThisUsedFlag or cFillerFlag)) <> (cThisUsedFlag or cFillerFlag) then
    heapErrorCode := cBadFiller1;
  result := sizeFlags and not cFlags;
  Dec(a, result);
  if ((PUsed(a).sizeFlags xor sizeFlags) and not cPrevFreeFlag) <> 0 then
    HeapErrorCode := cBadFiller2;
  if (PUsed(a).sizeFlags and cPrevFreeFlag) <> 0 then begin
    freeSize := PFree(a - sizeof(TFree)).size;
    f := PFree(a - freeSize);
    if f.size <> freeSize then
      heapErrorCode := cBadFiller3;
    DeleteFree(f);
    Inc(result, freeSize);
  end;
end;


function FillerSizeAfterGap(a: PChar): Integer;
var
  sizeFlags: Integer;
  f : PFree;
begin
  result := 0;
  sizeFlags := PUsed(a).sizeFlags;
  if (sizeFlags and cFillerFlag) <> 0 then begin
    sizeFlags := sizeFlags and not cFlags;
    Inc(result,sizeFlags);
    Inc(a, sizeFlags);
    sizeFlags := PUsed(a).sizeFlags;
  end;
  if (sizeFlags and cThisUsedFlag) = 0 then begin
    f := PFree(a);
    DeleteFree(f);
    Inc(result, f.size);
    Inc(a, f.size);
    PUsed(a).sizeFlags := PUsed(a).sizeFlags and not cPrevFreeFlag;
  end;
end;


function DecommitFree(a: PChar; size: Integer): Boolean;
var
  b: TBlock;
  bd: PBlockDesc;
begin
  Result := False;
  bd := FindCommitted(a);
  if bd = nil then Exit;
  if bd.addr + bd.size - (a + size) <= sizeof(TFree) then
    size := bd.addr + bd.size - a;

  if a - bd.addr < sizeof(TFree) then
    b := FreeCommitted(bd.addr, size + (a - bd.addr))
  else
    b := FreeCommitted(a + sizeof(TUsed), size - sizeof(TUsed));

  if b.addr <> nil then
  begin
    FillBeforeGap(a, b.addr - a);
    if bd.addr + bd.size > b.addr + b.size then
      FillAfterGap(b.addr + b.size, a + size - (b.addr + b.size));
    RemoveBlock(bd,b);
    result := True;
  end;
end;


procedure InsertFree(a: Pointer; size: Integer);
var
  f, n, p: PFree;
begin
  f := PFree(a);
  f.size := size;
  PFree(PChar(f) + size - sizeof(TFree)).size := size;
  if size <= cSmallSize then begin
    n := smallTab[size div cAlign];
    if n = nil then begin
      smallTab[size div cAlign] := f;
      f.next := f;
      f.prev := f;
    end else begin
      p := n.prev;
      f.next := n;
      f.prev := p;
      n.prev := f;
      p.next := f;
    end;
  end else if (size < cDecommitMin) or not DecommitFree(a, size) then begin
    n := rover;
    rover := f;
    p := n.prev;
    f.next := n;
    f.prev := p;
    n.prev := f;
    p.next := f;
  end;
end;


procedure FreeCurAlloc;
begin
  if remBytes > 0 then begin
    if remBytes < sizeof(TFree) then
      heapErrorCode := cBadCurAlloc
    else begin
      PUsed(curAlloc).sizeFlags := remBytes or cThisUsedFlag;
      InternalFreeMem(curAlloc + sizeof(TUsed));
      curAlloc := nil;
      remBytes := 0;
    end;
  end;
end;


function MergeCommit(b: TBlock): Boolean;
var
  merged: TBlock;
  fSize: Integer;
begin
  FreeCurAlloc;
  merged := MergeBlockAfter(@committedRoot, b);
  if merged.addr = nil then begin
    result := False;
    exit;
  end;

  if merged.addr < b.addr then begin
    fSize := FillerSizeBeforeGap(b.addr);
    Dec(b.addr, fSize);
    Inc(b.size, fSize);
  end;

  if merged.addr + merged.size > b.addr + b.size then begin
    fSize := FillerSizeAfterGap(b.addr + b.size);
    Inc(b.size, fSize);
  end;

  if merged.addr + merged.size = b.addr + b.size then begin
    FillBeforeGap(b.addr + b.size - sizeof(TUsed), sizeof(TUsed));
    Dec(b.size, sizeof(TUsed));
  end;

  curAlloc := b.addr;
  remBytes := b.size;

  result := True;
end;


function NewCommit(minSize: Integer): Boolean;
var
  b: TBlock;
begin
  b := GetCommitted(minSize+sizeof(TUsed));
  result := (b.addr <> nil) and MergeCommit(b);
end;


function NewCommitAt(addr: Pointer; minSize: Integer): Boolean;
var
  b: TBlock;
begin
  b := GetCommittedAt(addr, minSize+sizeof(TUsed));
  result := (b.addr <> nil) and MergeCommit(b);
end;


function SearchSmallBlocks(size: Integer): PFree;
var
  i: Integer;
begin
  result := nil;
  for i := size div cAlign to High(smallTab^) do begin
    result := smallTab[i];
    if result <> nil then
      exit;
  end;
end;


function TryHarder(size: Integer): Pointer;
var
  u: PUsed; f:PFree; saveSize, rest: Integer;
begin

  repeat

    f := avail.next;
    if (size <= f.size) then
      break;

    f := rover;
    if f.size >= size then
      break;

    saveSize := f.size;
    f.size := size;
    repeat
      f := f.next
    until f.size >= size;
    rover.size := saveSize;
    if f <> rover then begin
      rover := f;
      break;
    end;

    if size <= cSmallSize then begin
      f := SearchSmallBlocks(size);
      if f <> nil then
        break;
    end;

    if not NewCommit(size) then begin
      result := nil;
      exit;
    end;

    if remBytes >= size then begin
      Dec(remBytes, size);
      if remBytes < sizeof(TFree) then begin
        Inc(size, remBytes);
        remBytes := 0;
      end;
      u := PUsed(curAlloc);
      Inc(curAlloc, size);
      u.sizeFlags := size or cThisUsedFlag;
      result := PChar(u) + sizeof(TUsed);
      Inc(AllocMemCount);
      Inc(AllocMemSize,size - sizeof(TUsed));
      exit;
    end;

  until False;

  DeleteFree(f);

  rest := f.size - size;
  if rest >= sizeof(TFree) then begin
    InsertFree(PChar(f) + size, rest);
  end else begin
    size := f.size;
    if f = rover then
      rover := f.next;
    u := PUsed(PChar(f) + size);
    u.sizeFlags := u.sizeFlags and not cPrevFreeFlag;
  end;

  u := PUsed(f);
  u.sizeFlags := size or cThisUsedFlag;

  result := PChar(u) + sizeof(TUsed);
  Inc(AllocMemCount);
  Inc(AllocMemSize,size - sizeof(TUsed));

end;


function SysGetMem(size: Integer): Pointer;
// Allocate memory block.
var
  f, prev, next: PFree;
  u: PUsed;
begin

  if (not initialized and not InitAllocator) or
    (size > (High(size) - (sizeof(TUsed) + (cAlign-1)))) then
  begin
    result := nil;
    exit;
  end;


  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    Inc(size, sizeof(TUsed) + (cAlign-1));
    size := size and not (cAlign-1);
    if size < sizeof(TFree) then
      size := sizeof(TFree);

    if size <= cSmallSize then begin
      f := smallTab[size div cAlign];
      if f <> nil then begin
        u := PUsed(PChar(f) + size);
        u.sizeFlags := u.sizeFlags and not cPrevFreeFlag;
        next := f.next;
        if next = f then
          smallTab[size div cAlign] := nil
        else begin
          smallTab[size div cAlign] := next;
          prev := f.prev;
          prev.next := next;
          next.prev := prev;
        end;
        u := PUsed(f);
        u.sizeFlags := f.size or cThisUsedFlag;
        result := PChar(u) + sizeof(TUsed);
        Inc(AllocMemCount);
        Inc(AllocMemSize,size - sizeof(TUsed));
        exit;
      end;
    end;

    if size <= remBytes then begin
      Dec(remBytes, size);
      if remBytes < sizeof(TFree) then begin
        Inc(size, remBytes);
        remBytes := 0;
      end;
      u := PUsed(curAlloc);
      Inc(curAlloc, size);
      u.sizeFlags := size or cThisUsedFlag;
      result := PChar(u) + sizeof(TUsed);
      Inc(AllocMemCount);
      Inc(AllocMemSize,size - sizeof(TUsed));
      exit;
    end;

    result := TryHarder(size);

  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;

end;


function SysFreeMem(p: Pointer): Integer;
// Deallocate memory block.
label
  abort;
var
  u, n : PUsed;
  f : PFree;
  prevSize, nextSize, size : Integer;
begin
  heapErrorCode := cHeapOk;

  if not initialized and not InitAllocator then begin
    heapErrorCode := cCantInit;
    result := cCantInit;
    exit;
  end;

  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    u := p;
    u := PUsed(PChar(u) - sizeof(TUsed)); { inv: u = address of allocated block being freed }
    size := u.sizeFlags;
    { inv: size = SET(block size) + [block flags] }

    { validate that the interpretation of this block as a used block is correct }
    if (size and cThisUsedFlag) = 0 then begin
      heapErrorCode := cBadUsedBlock;
      goto abort;
    end;

    { inv: the memory block addressed by 'u' and 'p' is an allocated block }

    Dec(AllocMemCount);
    Dec(AllocMemSize,size and not cFlags - sizeof(TUsed));

    if (size and cPrevFreeFlag) <> 0 then begin
      { previous block is free, coalesce }
      prevSize := PFree(PChar(u)-sizeof(TFree)).size;
      if (prevSize < sizeof(TFree)) or ((prevSize and cFlags) <> 0) then begin
        heapErrorCode := cBadPrevBlock;
        goto abort;
      end;

      f := PFree(PChar(u) - prevSize);
      if f^.size <> prevSize then begin
        heapErrorCode := cBadPrevBlock;
        goto abort;
      end;

      inc(size, prevSize);
      u := PUsed(f);
      DeleteFree(f);
    end;

    size := size and not cFlags;
    { inv: size = block size }

    n := PUsed(PChar(u) + size);
    { inv: n = block following the block to free }

    if PChar(n) = curAlloc then begin
      { inv: u = last block allocated }
      dec(curAlloc, size);
      inc(remBytes, size);
      if remBytes > cDecommitMin then
        FreeCurAlloc;
      result := cHeapOk;
      exit;
    end;

    if (n.sizeFlags and cThisUsedFlag) <> 0 then begin
      { inv: n is a used block }
      if (n.sizeFlags and not cFlags) < sizeof(TUsed) then begin
        heapErrorCode := cBadNextBlock;
        goto abort;
      end;
      n.sizeFlags := n.sizeFlags or cPrevFreeFlag
    end else begin
      { inv: block u & n are both free; coalesce }
      f := PFree(n);
      if (f.next = nil) or (f.prev = nil) or (f.size < sizeof(TFree)) then begin
        heapErrorCode := cBadNextBlock;
        goto abort;
      end;
      nextSize := f.size;
      inc(size, nextSize);
      DeleteFree(f);
      { inv: last block (which was free) is not on free list }
    end;

    InsertFree(u, size);
abort:
    result := heapErrorCode;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
end;


function ResizeInPlace(p: Pointer; newSize: Integer): Boolean;
var u, n: PUsed; f: PFree; oldSize, blkSize, neededSize: Integer;
begin
  Inc(newSize, sizeof(TUsed)+cAlign-1);
  newSize := newSize and not (cAlign-1);
  if newSize < sizeof(TFree) then
    newSize := sizeof(TFree);
  u := PUsed(PChar(p) - sizeof(TUsed));
  oldSize := u.sizeFlags and not cFlags;
  n := PUsed( PChar(u) + oldSize );
  if newSize <= oldSize then begin
    blkSize := oldSize - newSize;
    if PChar(n) = curAlloc then begin
      Dec(curAlloc, blkSize);
      Inc(remBytes, blkSize);
      if remBytes < sizeof(TFree) then begin
        Inc(curAlloc, blkSize);
        Dec(remBytes, blkSize);
        newSize := oldSize;
      end;
    end else begin
      n := PUsed(PChar(u) + oldSize);
      if n.sizeFlags and cThisUsedFlag = 0 then begin
        f := PFree(n);
        Inc(blkSize, f.size);
        DeleteFree(f);
      end;
      if blkSize >= sizeof(TFree) then begin
        n := PUsed(PChar(u) + newSize);
        n.sizeFlags := blkSize or cThisUsedFlag;
        InternalFreeMem(PChar(n) + sizeof(TUsed));
      end else
        newSize := oldSize;
    end;
  end else begin
    repeat
      neededSize := newSize - oldSize;
      if PChar(n) = curAlloc then begin
        if remBytes >= neededSize then begin
          Dec(remBytes, neededSize);
          Inc(curAlloc, neededSize);
          if remBytes < sizeof(TFree) then begin
            Inc(curAlloc, remBytes);
            Inc(newSize, remBytes);
            remBytes := 0;
          end;
          Inc(AllocMemSize, newSize - oldSize);
          u.sizeFlags := newSize or u.sizeFlags and cFlags;
          result := true;
          exit;
        end else begin
          FreeCurAlloc;
          n := PUsed( PChar(u) + oldSize );
        end;
      end;

      if n.sizeFlags and cThisUsedFlag = 0 then begin
        f := PFree(n);
        blkSize := f.size;
        if blkSize < neededSize then begin
          n := PUsed(PChar(n) + blkSize);
          Dec(neededSize, blkSize);
        end else begin
          DeleteFree(f);
          Dec(blkSize, neededSize);
          if blkSize >= sizeof(TFree) then
            InsertFree(PChar(u) + newSize, blkSize)
          else begin
            Inc(newSize, blkSize);
            n := PUsed(PChar(u) + newSize);
            n.sizeFlags := n.sizeFlags and not cPrevFreeFlag;
          end;
          break;
        end;
      end;

      if n.sizeFlags and cFillerFlag <> 0 then begin
        n := PUsed(PChar(n) + n.sizeFlags and not cFlags);
        if NewCommitAt(n, neededSize) then begin
          n := PUsed( PChar(u) + oldSize );
          continue;
        end;
      end;

      result := False;
      exit;

    until False;

  end;

  Inc(AllocMemSize, newSize - oldSize);
  u.sizeFlags := newSize or u.sizeFlags and cFlags;
  result := True;

end;


function SysReallocMem(p: Pointer; size: Integer): Pointer;
// Resize memory block.
var
  n: Pointer; oldSize: Integer;
begin

  if not initialized and not InitAllocator then begin
    result := nil;
    exit;
  end;

  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    if ResizeInPlace(p, size) then
      result := p
    else begin
      n := SysGetMem(size);
      oldSize := (PUsed(PChar(p)-sizeof(PUsed)).sizeFlags and not cFlags) - sizeof(TUsed);
      if oldSize > size then
        oldSize := size;
      if n <> nil then begin
        Move(p^, n^, oldSize);
        SysFreeMem(p);
      end;
      result := n;
    end;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;

end;


function BlockSum(root: PBlockDesc): Integer;
var
  b : PBlockDesc;
begin
  result := 0;
  b := root.next;
  while b <> root do begin
    Inc(result, b.size);
    b := b.next;
  end;
end;


function GetHeapStatus: THeapStatus;
var
  size, freeSize, userSize: Cardinal;
  f: PFree;
  a, e: PChar;
  i: Integer;
  b: PBlockDesc;
  prevFree: Boolean;
begin

  result.TotalAddrSpace   := 0;
  result.TotalUncommitted := 0;
  result.TotalCommitted   := 0;
  result.TotalAllocated   := 0;
  result.TotalFree        := 0;
  result.FreeSmall        := 0;
  result.FreeBig          := 0;
  result.Unused           := 0;
  result.Overhead         := 0;
  result.HeapErrorCode    := cHeapOk;

  if not initialized then exit;

  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    result.totalAddrSpace   := BlockSum(@spaceRoot);
    result.totalUncommitted := BlockSum(@decommittedRoot);
    result.totalCommitted   := BlockSum(@committedRoot);

    size := 0;
    for i := Low(smallTab^) to High(smallTab^) do begin
      f := smallTab[i];
      if f <> nil then begin
        repeat
          Inc(size, f.size);
          if (f.prev.next <> f) or (f.next.prev <> f) or (f.size < sizeof(TFree)) then begin
            heapErrorCode := cBadFreeList;
            break;
          end;
          f := f.next;
        until f = smallTab[i];
      end;
    end;
    result.freeSmall := size;

    size := 0;
    f := avail.next;
    while f <> @avail do begin
      if (f.prev.next <> f) or (f.next.prev <> f) or (f.size < sizeof(TFree)) then begin
        heapErrorCode := cBadFreeList;
        break;
      end;
      Inc(size, f.size);
      f := f.next;
    end;
    result.freeBig := size;

    result.unused := remBytes;
    result.totalFree := result.freeSmall + result.freeBig + result.unused;

    freeSize := 0;
    userSize := 0;
    result.overhead := 0;

    b := committedRoot.next;
    prevFree := False;
    while b <> @committedRoot do begin
      a := b.addr;
      e := a + b.size;
      while a < e do begin
        if (a = curAlloc) and (remBytes > 0) then begin
          size := remBytes;
          Inc(freeSize, size);
          if prevFree then
            heapErrorCode := cBadCurAlloc;
          prevFree := False;
        end else begin
          if prevFree <> ((PUsed(a).sizeFlags and cPrevFreeFlag) <> 0) then
            heapErrorCode := cBadNextBlock;
          if (PUsed(a).sizeFlags and cThisUsedFlag) = 0 then begin
            f := PFree(a);
            if (f.prev.next <> f) or (f.next.prev <> f) or (f.size < sizeof(TFree)) then
              heapErrorCode := cBadFreeBlock;
            size := f.size;
            Inc(freeSize, size);
            prevFree := True;
          end else begin
            size := PUsed(a).sizeFlags and not cFlags;
            if (PUsed(a).sizeFlags and cFillerFlag) <> 0 then begin
              Inc(result.overhead, size);
              if (a > b.addr) and (a + size < e) then
                heapErrorCode := cBadUsedBlock;
          end else begin
            Inc(userSize, size-sizeof(TUsed));
            Inc(result.overhead, sizeof(TUsed));
          end;
          prevFree := False;
        end;
      end;
      Inc(a, size);
      end;
      b := b.next;
    end;
    if result.totalFree <> freeSize then
      heapErrorCode := cBadBalance;

    result.totalAllocated := userSize;
    result.heapErrorCode := heapErrorCode;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
end;


//  this section goes into GetMem.Inc

{$IFDEF DEBUG_FUNCTIONS}
type
  THeapReportProc = procedure(HeapBlock: Pointer; AllocatedSize: Integer) of object;


procedure WalkHeap(HeapReportProc: THeapReportProc);
var
  size : Cardinal;
  f: PFree;
  a, e: PChar;
  b: PBlockDesc;
begin

  if not initialized then exit;

  try
    if IsMultiThread then EnterCriticalSection(heapLock);

    b := committedRoot.next;
    while b <> @committedRoot do begin
      a := b.addr;
      e := a + b.size;
      while a < e do begin
        if (a = curAlloc) and (remBytes > 0) then begin
          size := remBytes;
        end else begin
          if (PUsed(a).sizeFlags and cThisUsedFlag) = 0 then begin
            f := PFree(a);
            size := f.size;
          end else begin
            size := PUsed(a).sizeFlags and not cFlags;
            if (PUsed(a).sizeFlags and cFillerFlag) = 0 then begin
              HeapReportProc(a + sizeof(TUsed), size - sizeof(TUsed));
            end;
          end;
        end;
        Inc(a, size);
      end;
      b := b.next;
    end;
  finally
    if IsMultiThread then LeaveCriticalSection(heapLock);
  end;
end;

type
  THeapBlockCollector = class(TObject)
    FCount: Integer;
    FObjectTable: TObjectArray;
    FHeapBlockTable: THeapBlockArray;
    FClass: TClass;
    FFindDerived: Boolean;
    procedure CollectBlocks(HeapBlock: Pointer; AllocatedSize: Integer);
    procedure CollectObjects(HeapBlock: Pointer; AllocatedSize: Integer);
  end;


procedure THeapBlockCollector.CollectBlocks(HeapBlock: Pointer; AllocatedSize: Integer);
begin
  if FCount < Length(FHeapBlockTable) then
  begin
    FHeapBlockTable[FCount].Start := HeapBlock;
    FHeapBlockTable[FCount].Size  := AllocatedSize;
  end;
  Inc(FCount);
end;


procedure THeapBlockCollector.CollectObjects(HeapBlock: Pointer; AllocatedSize: Integer);
var
  AObject: TObject;
  AClass: TClass;
type
  PPointer = ^Pointer;
begin
  try
    if AllocatedSize < 4 then
      Exit;
    AObject := TObject(HeapBlock);
    AClass := AObject.ClassType;
    if (AClass = FClass)
      or (FFindDerived
        and (Integer(AClass) >= 64*1024)
        and (PPointer(PChar(AClass) + vmtSelfPtr)^ = Pointer(AClass))
        and (AObject is FClass)) then
    begin
      if FCount < Length(FObjectTable) then
        FObjectTable[FCount] := AObject;
      Inc(FCount);
    end;
  except
  //  Let's not worry about this block - it's obviously not a valid object
  end;
end;

var
  HeapBlockCollector: THeapBlockCollector;

function GetHeapBlocks: THeapBlockArray;
begin
  if not Assigned(HeapBlockCollector) then
    HeapBlockCollector := THeapBlockCollector.Create;

  Walkheap(HeapBlockCollector.CollectBlocks);
  SetLength(HeapBlockCollector.FHeapBlockTable, HeapBlockCollector.FCount);
  HeapBlockCollector.FCount := 0;
  Walkheap(HeapBlockCollector.CollectBlocks);
  Result := HeapBlockCollector.FHeapBlockTable;
  HeapBlockCollector.FCount := 0;
  HeapBlockCollector.FHeapBlockTable := nil;
end;


function FindObjects(AClass: TClass; FindDerived: Boolean): TObjectArray;
begin
  if not Assigned(HeapBlockCollector) then
    HeapBlockCollector := THeapBlockCollector.Create;
  HeapBlockCollector.FClass := AClass;
  HeapBlockCollector.FFindDerived := FindDerived;

  Walkheap(HeapBlockCollector.CollectObjects);
  SetLength(HeapBlockCollector.FObjectTable, HeapBlockCollector.FCount);
  HeapBlockCollector.FCount := 0;
  Walkheap(HeapBlockCollector.CollectObjects);
  Result := HeapBlockCollector.FObjectTable;
  HeapBlockCollector.FCount := 0;
  HeapBlockCollector.FObjectTable := nil;
end;
{$ENDIF}


